package com.jp.fm.service;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.math.NumberUtils;

import com.jp.fm.dao.AccountDao;
import com.jp.fm.dao.CategoryDao;
import com.jp.fm.dao.TransactionDao;
import com.jp.fm.data.Account;
import com.jp.fm.data.Category;
import com.jp.fm.data.Transaction;

public class TransactionService {

	private static final int UNCATEGORIZED_TRNASACTION_HISTORY_YEARS = -2;

	private TransactionDao transactionDao = null;
	private AccountDao accountDao = null;
	private CategoryDao categoryDao = null;

	public Float calculateTransactionsTotal(final List<Transaction> trans) {

		List<Category> cats = new ArrayList<Category>();

		return calculateTransactionsTotal(trans, cats);
	}

	public Float calculateTransactionsTotal(final List<Transaction> trans,
			final Category matchingCategory) {

		List<Category> cats = new ArrayList<Category>();
		cats.add(matchingCategory);

		return calculateTransactionsTotal(trans, cats);
	}

	/**
	 * Normalizes the total based on if the account is checking, savings or
	 * credit card. If the matchingCategories list is not null it will be used
	 * to find the total for the passed in categories.
	 * @param trans
	 * @return The normalized total
	 */
	public Float calculateTransactionsTotal(final List<Transaction> trans,
			final List<Category> matchingCategories) {

		Float categoryAmount = Float.valueOf("0");
		boolean checkChildren = false;

		for (Iterator<Transaction> iterator2 = trans.iterator(); iterator2
				.hasNext();) {
			Transaction transaction = iterator2.next();

			// Get the account and determine if we need to add or subtract
			// amount
			Account acct = accountDao.findByName(transaction.getAccount());

			if (null != matchingCategories && matchingCategories.size() > 0
					&& transaction.getCompositeTransactions().size() > 0
					&& !matchingCategories.contains(transaction.getCategory())) {
				checkChildren = true;
			}

			Float amt = NumberUtils.FLOAT_ZERO;

			if (checkChildren) {
				amt = calculateTransactionsTotal(
						transaction.getCompositeTransactions(),
						matchingCategories);
			} else if (null != matchingCategories
					&& matchingCategories.size() > 0
					&& matchingCategories.contains(transaction.getCategory())) {
				amt = transaction.getAmount();
				if ("Savings".equals(acct.getType().getName())
						|| "Checking".equals(acct.getType().getName())) {
					amt *= -1;
				}
			} else if (null == matchingCategories
					|| matchingCategories.size() == 0) {
				amt = transaction.getAmount();
				if ("Savings".equals(acct.getType().getName())
						|| "Checking".equals(acct.getType().getName())) {
					amt *= -1;
				}
			}

			categoryAmount += amt;
			checkChildren = false;
		}

		if (categoryAmount < 0) {
			categoryAmount *= -1;
		}

		return categoryAmount;
	}

	public List<Transaction> searchTransactionsByDate(final Date startDate,
			final Date endDate, final String acct) {

		List<Transaction> trans = new ArrayList<Transaction>();

		// get the transactions
		if ("all".equals(acct)) {
			trans = new ArrayList<Transaction>();
			for (Iterator<Account> iterator = accountDao.findAll().iterator(); iterator
					.hasNext();) {
				Account account = iterator.next();

				trans.addAll(transactionDao.findByAny(null, startDate, endDate,
						account.getName(), null, null));

			}
		} else {
			trans = transactionDao.findByAny(null, startDate, endDate, acct,
					null, null);
		}

		return trans;
	}

	public List<String> findUnconfirmedTransactions() {
		List<String> ret = new ArrayList<String>();

		List<Transaction> trans = transactionDao.findByAny(null, null, null,
				null, null, Boolean.FALSE);

		if (null != trans && trans.size() > 0) {

			ret.add("You have " + trans.size()
					+ " <a class=\"click-link\" url=\"/fm/secured/"
					+ "ShowTransactions?searchCategoryUnconfirmed=Y\""
					+ ">unconfirmed</a> transactions");
		}

		return ret;
	}

	public List<String> findUncategorizedTransactions() {

		List<String> ret = new ArrayList<String>();

		Calendar now = new GregorianCalendar();
		Calendar minus2 = new GregorianCalendar();
		minus2.set(Calendar.YEAR, UNCATEGORIZED_TRNASACTION_HISTORY_YEARS);

		List<Transaction> trans = transactionDao.findByAny(
				categoryDao.findByName("Unknown"), minus2.getTime(),
				now.getTime(), null, null, null);

		if (null != trans && trans.size() > 0) {
			ret.add("You have " + trans.size()
					+ " <a class=\"click-link\" url=\"/fm/secured/"
					+ "ShowTransactions?categoryThisMonth=Unknown\""
					+ ">uncategorized</a> transactions");
		}

		return ret;
	}

	public List<String> findFeeTransactions() {

		List<String> ret = new ArrayList<String>();

		Calendar now = new GregorianCalendar();
		Calendar monthFirst = new GregorianCalendar();
		monthFirst.set(Calendar.DAY_OF_MONTH, 1);

		List<Transaction> trans = transactionDao.findByAny(
				categoryDao.findByName("Fees"), monthFirst.getTime(),
				now.getTime(), null, null, null);

		if (null != trans && trans.size() > 0) {

			Float totalFees = Float.valueOf("0");
			for (Iterator<Transaction> iterator = trans.iterator(); iterator
					.hasNext();) {
				Transaction transaction = iterator.next();
				totalFees += transaction.getAmount();
			}

			totalFees *= -1;

			ret.add("You have paid "
					+ NumberFormat.getCurrencyInstance().format(totalFees)
					+ " in <a class=\"click-link\" url=\"/fm/secured/"
					+ "ShowTransactions?categoryThisMonth=Fees\""
					+ ">Fees</a> this month");
		}

		return ret;
	}

	public final void setTransactionDao(final TransactionDao transactionDao) {
		this.transactionDao = transactionDao;
	}

	public final void setAccountDao(final AccountDao accountDao) {
		this.accountDao = accountDao;
	}

	public final void setCategoryDao(final CategoryDao categoryDao) {
		this.categoryDao = categoryDao;
	}
}
